/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.util;

import java.io.UnsupportedEncodingException;
import java.util.Random;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a>
 * @since 2.0.0 <br/>
 */
public final class ObfuscatedString {

	private static final String UTF8 = new String(new char[] { '\u0055',
			'\u0054', '\u0046', '\u0038' }); // => "UTF8"

	public static String obfuscate(final String s) {
		if (-1 != s.indexOf(0)) {
			throw new IllegalArgumentException(new ObfuscatedString(new long[] {
					0x241005931110FC70L, 0xDCD925A88EAD9F37L,
					0x19ADA1C861E2A85DL, 0x9A5948E700FCAD8AL,
					0x2E11C83A72441DE2L }).toString()); // => "Null characters
			// are not allowed!";
		}
		final byte[] encoded;
		try {
			encoded = s.getBytes(UTF8);
		} catch (UnsupportedEncodingException ex) {
			throw new AssertionError(ex);
		}
		long seed;
		Random prng = new Random();
		do {
			seed = prng.nextLong();
		} while (seed == 0);
		prng = new Random(seed);
		final StringBuilder code = new StringBuilder(new ObfuscatedString(
				new long[] { 0xA28E32BB0D3E394EL, 0xF842D1C94E549EECL,
						0x7D07DFF01F907E4L, 0x4E0BDE791ECD467CL,
						0xDFF389B58DA3E44FL, 0x2477FAED0CE62C79L }).toString()); // =>
		// "new
		// ObfuscatedString(new
		// long[]
		// {";
		appendHexLiteral(code, seed);

		final int length = encoded.length;
		for (int i = 0; i < length; i += 8) {
			final long key = prng.nextLong();
			// Compute the value of the next array element as an obfuscated
			// version of the next eight bytes of the UTF8 encoded string.
			final long obfuscated = toLong(encoded, i) ^ key;

			code.append(", ");
			appendHexLiteral(code, obfuscated);
		}

		code
				.append(new ObfuscatedString(new long[] { 0x4200B7AD6FFFF546L,
						0x9B822E95FE73769DL, 0x23C2800C6CACFCE3L,
						0x21C30B492D9AEF99L }).toString()); // => "}).toString()
		// /* => \"";

		code.append(s.replaceAll(
				"\\\\",
				new ObfuscatedString(new long[] { 0x6D2C680D49523A01L,
						0xB932F1DBD19E82CEL }).toString() /*
															 * => "\\\\\\\\"
															 */).replaceAll(
				"\"",
				new ObfuscatedString(new long[] { 0x85E9D53EF7A9324BL,
						0xB05BD65C9F19DE07L }).toString() /*
															 * => "\\\\\""
															 */));

		code.append(new ObfuscatedString(new long[] { 0xC54FFF0621E7D107L,
				0x194EAD468C6FCF93L }).toString()); // => "\" */"

		return code.toString();
	}

	private static void appendHexLiteral(final StringBuilder sb, final long l) {
		sb.append('0');
		sb.append('x');
		sb.append(Long.toHexString(l).toUpperCase());
		sb.append('L');
	}

	private static long toLong(final byte[] bytes, int off) {
		final int end = Math.min(bytes.length, off + 8);
		long l = 0;
		for (int i = end; --i >= off;) {
			l <<= 8;
			l |= bytes[i] & 0xFF;
		}
		return l;
	}

	private static void toBytes(long l, byte[] bytes, int off) {
		final int end = Math.min(bytes.length, off + 8);
		for (int i = off; i < end; i++) {
			bytes[i] = (byte) l;
			l >>= 8;
		}
	}

	private final long[] obfuscated;

	public ObfuscatedString(final long[] obfuscated) {
		this.obfuscated = (long[]) obfuscated.clone();
		this.obfuscated[0] = obfuscated[0];
	}

	public String toString() {
		final int length = obfuscated.length;
		final byte[] encoded = new byte[8 * (length - 1)];
		final long seed = obfuscated[0];
		final Random prng = new Random(seed);
		for (int i = 1; i < length; i++) {
			final long key = prng.nextLong();
			toBytes(obfuscated[i] ^ key, encoded, 8 * (i - 1));
		}
		final String decoded;
		try {
			decoded = new String(encoded, UTF8);
		} catch (UnsupportedEncodingException ex) {
			throw new AssertionError(ex);
		}
		final int i = decoded.indexOf(0);
		return -1 == i ? decoded : decoded.substring(0, i);
	}
}
